package ptij.ADSL.de.ATUR;

import ptolemy.kernel.*;
import ptolemy.kernel.util.*;
import ptolemy.data.*;
import ptolemy.data.type.*;
import ptolemy.data.expr.*;
import ptolemy.actor.*;
import ptolemy.math.Complex;

import ptij.ADSL.common.FastFECDataFrame;

public class Demap extends TypedAtomicActor {        
        
    public Demap(CompositeEntity container, String name) throws 
           NameDuplicationException, IllegalActionException {        
	super(container, name);

	fast = new TypedIOPort(this, "fast", false, true);
	fast.setTypeEquals(BaseType.OBJECT);
	
	inter = new TypedIOPort(this, "inter", false, true);
	inter.setTypeEquals(BaseType.OBJECT);

	AS0Bytes = new Parameter(this, "AS0Bytes", new IntToken(8));
	LS0Bytes = new Parameter(this, "LS0Bytes", new IntToken(8));
	
	AS0Map = new Parameter(this, "AS0Map", new BooleanToken(false));
	LS0Map = new Parameter(this, "LS0Map", new BooleanToken(false));
	
	S = new Parameter(this, "S", new IntToken(1));

        parityFast = new Parameter(this, "parityFast", new IntToken(2));
	parityInter = new Parameter(this, "parityInter", new IntToken(2));
	
        inputMode = new Parameter(this, "inputMode", new IntToken(1));        
        _inputMode = 1;
        
        _createInputInterface();
    }

    ///////////////////////////////////////////////////////////////////
    ////                     ports and parameters                  ////
    
    public TypedIOPort subChannel[];	
    public TypedIOPort fast;
    public TypedIOPort inter;

    public Parameter AS0Bytes;
    public Parameter LS0Bytes;    
    public Parameter AS0Map;
    public Parameter LS0Map;    
    public Parameter S;
    public Parameter parityFast;
    public Parameter parityInter;
    public Parameter inputMode;

    ///////////////////////////////////////////////////////////////////
    ////                         public methods                    ////

    public void attributeChanged(Attribute attribute)
            throws IllegalActionException {
        if(attribute == AS0Bytes) {
	    IntToken it = (IntToken) AS0Bytes.getToken();
	    _AS0Bytes = it.intValue();            
        }
        else
        if(attribute == LS0Bytes) {
            IntToken it = (IntToken) LS0Bytes.getToken();
            _LS0Bytes = it.intValue();                    
        }
        if(attribute == AS0Map) {
  	    BooleanToken _bt = (BooleanToken) AS0Map.getToken();
 	    _AS0Map = _bt.booleanValue();            
        }
        else
        if(attribute == LS0Map) {
            BooleanToken bt = (BooleanToken) LS0Map.getToken();
	    _LS0Map = bt.booleanValue();   
        }
        else
        if(attribute == S) {
 	    IntToken it = (IntToken) S.getToken();
	    _S = it.intValue();           
        }
        if(attribute == parityFast) {
	    IntToken it = (IntToken) parityFast.getToken();
            _nnFast = _kkFast + it.intValue();
        }
        else
        if(attribute == parityInter) {
	    IntToken it = (IntToken) parityInter.getToken();
            _nnInter = _kkInter + it.intValue();            
        }        
        else
        if(attribute == inputMode) {
            /*IntToken it = (IntToken) inputMode.getToken();
            _inputMode = it.intValue();
            try {
                _createInputInterface();
            }
            catch(NameDuplicationException ex) {
                ex.printStackTrace(System.out);
            } */           
        }        
	else {
            super.attributeChanged(attribute);
        }                
    }
    
    public void initialize() throws IllegalActionException {
        super.initialize();

	_kkFast = _kkInter = 1;
	if(!_AS0Map) {
	    _kkFast += _AS0Bytes;
	}
	else {
	    _kkInter += _AS0Bytes;
	}
	
	if(!_LS0Map) {
	    _kkFast += _LS0Bytes;
	}
	else {
	    _kkInter += _LS0Bytes;
	}
	
	if(!_AS0Map /* || !_AS1Map || ... */) {
 	    _kkFast += 1;
	}	
	if(!_AS0Map || !_LS0Map) {
	    _kkFast += 1;
	}
	
	if(_AS0Map) {
 	    _kkInter += 1;
	}	
	if(_AS0Map || _LS0Map) {
	    _kkInter += 1;
	}
        _kkInter *= _S;			
	
	_bitTable = new int[255];
	_subChannelTable = new int[255];
	_gainTable = new Complex[255];
	
	int tot = (_nnFast + _nnInter) * 8;
	for(int i = 0;i < 255;i++) {
	    _subChannelTable[i] = i;
	    if(i == 63) {
	        _bitTable[i] = 0;
		_gainTable[i] = new Complex(1,1);
	        continue;
	    }
	    _gainTable[i] = new Complex(1, 1);

            if(tot > 4) {
	        _bitTable[i] = 5;
	        tot -= 5;
	    }
	    else
	    if(tot > 0) {
	        _bitTable[i] = tot;
	        tot = 0;
	    }
	    else {
	        _bitTable[i] = 0;
	    }
	}
	
	_checkParameters();
		
	_msbTable = new int[64];
	
	_msbTable[0] = 0;
	_msbTable[16] = 0;
	_msbTable[32] = 0;
	_msbTable[48] = 0;
	_msbTable[3] = 1;
	_msbTable[19] = 1;
	_msbTable[35] = 1;
	_msbTable[51] = 1;
	_msbTable[12] = 2;
	_msbTable[28] = 2;
	_msbTable[44] = 2;
	_msbTable[60] = 2;
	_msbTable[15] = 3;
	_msbTable[31] = 3;
	_msbTable[47] = 3;
	_msbTable[63] = 3;
	_msbTable[4] = 4;
	_msbTable[20] = 4;
	_msbTable[40] = 4;
	_msbTable[56] = 4;
	_msbTable[1] = 5;
	_msbTable[18] = 5;
	_msbTable[33] = 5;
	_msbTable[50] = 5;
	_msbTable[13] = 6;
	_msbTable[30] = 6;
	_msbTable[45] = 6;
	_msbTable[62] = 6;
	_msbTable[7] = 7;
	_msbTable[23] = 7;
	_msbTable[43] = 7;
	_msbTable[59] = 7;
    }
    
    public void fire() throws IllegalActionException {    
        int bc = 0, wc = 0;
	int data[] = new int[_nnInter + _nnFast];
	
	if(_inputMode == 0) {
	    for(int i = 0;i < 255;i++) {
	        if(i == 63) {
	            subChannel[63].get(0);
		    subChannel[63].get(0);
	            continue;
	        }
	        DoubleToken dtr = (DoubleToken) 
	            subChannel[_subChannelTable[i]].get(0);
	        DoubleToken dti = (DoubleToken)
	            subChannel[_subChannelTable[i]].get(0);
	    
	        Complex Z = new Complex(dtr.doubleValue(), dti.doubleValue()); 
	        int b = _constellation(Z, _bitTable[i]);
	    
	        for(int j = 0;j < _bitTable[i];j++) {
	            data[wc] |= (((b >> j) & 0x01) << bc);
		    bc++;
		    if(bc == 8) {
		        bc = 0;
		        wc++;
		    }
	        }
	    }
	}
	else {
            ObjectToken ot = (ObjectToken) subChannel[0].get(0);
	    double [] indata = (double []) ot.getValue();
	    for(int i = 0;i < 255;i++) {
	        if(i == 63) {
	            continue;
	        }
	        Complex Z = new Complex(indata[2*i], indata[2*i + 1]); 
	        int b = _constellation(Z, _bitTable[i]);
	    
	        for(int j = 0;j < _bitTable[i];j++) {
	            data[wc] |= (((b >> j) & 0x01) << bc);
		    bc++;
		    if(bc == 8) {
		        bc = 0;
		        wc++;
		    }
	        }
	    }	
	}
	
	wc = 0;
	FastFECDataFrame fastf = new FastFECDataFrame(_AS0Map ? 0 : _AS0Bytes,
            0, 0, 0, _LS0Map ? 0 : _LS0Bytes, 0, 0, _nnFast - _kkFast);

        fastf.setFastByte(data[wc]);
	wc++;	
	if(!_AS0Map) {
            for(int i = 0;i < _AS0Bytes;i++) {
	        fastf.setAS0Byte(data[wc], i);
		wc++;
	    }
	}
	if(!_LS0Map) {
            for(int i = 0;i < _LS0Bytes;i++) {
	        fastf.setLS0Byte(data[wc], i);
		wc++;
	    }
	}
	if(fastf.hasAEX()) {
	    fastf.setAEXByte(data[wc]);
	    wc++;
	}
	if(fastf.hasLEX()) {
	    fastf.setLEXByte(data[wc]);
	    wc++;
	}
	for(int i = 0;i < (_nnFast - _kkFast);i++) {
	    fastf.setParityByte(data[wc], i);
	    wc++;
	}	
        fast.send(0, new ObjectToken(fastf));	

        int [] out = new int[_nnInter];
	for(int i = 0;i < _nnInter;i++) {
	    out[i] = data[wc];
	    wc++;
	}
	inter.broadcast(new ObjectToken(out));
    }    

    ///////////////////////////////////////////////////////////////////
    ////                         private methods                   ////
    
    private int _constellation(Complex Z, int nb) {
        if(nb == 0) {
	    return 0;
	}
	else
	if(nb == 1) {
	    if(Z.equals(new Complex(1, 1))) {
	        return 1;
	    }
	    else {
	        return 0;
	    }
	}
	else
	if(nb % 2 == 0) {
            int ret = 0, k = 1;
	    for(int i = 0;i < nb;i++) {
                if(i % 2 == 0) {
		    ret |= ((((int)Z.imag >> k) & 0x01) << i);
		}
		else {
		    ret |= ((((int)Z.real >> k) & 0x01) << i);
		    k++;
		}
	    }
	    return ret;
	}
	else
	if(nb == 3) {
	    if(Z.equals(new Complex(1, 1))) {
	        return 0;
	    }
	    else
	    if(Z.equals(new Complex(1, -1))) {
	        return 1;
	    }	    
	    else
	    if(Z.equals(new Complex(-1, 1))) {
	        return 2;
	    }
	    else
	    if(Z.equals(new Complex(-1, -1))) {
	        return 3;
	    }
	    else
	    if(Z.equals(new Complex(-3, 1))) {
	        return 4;
	    }
	    else
	    if(Z.equals(new Complex(1, 3))) {
	        return 5;
	    }	    
	    else
	    if(Z.equals(new Complex(-1, -3))) {
	        return 6;
	    }
	    else
	    if(Z.equals(new Complex(3, -1))) {
	        return 7;
	    }
	}
	else {
            int ret = 0, k = 1;
	    for(int i = 0;i < nb - 3;i++) {
                if(i % 2 == 0) {
		    ret |= ((((int)Z.imag >> k) & 0x01) << i);
		}
		else {
		    ret |= ((((int)Z.real >> k) & 0x01) << i);
		    k++;
		}
	    }
	    k--;
	    int tmp = 0;
	    tmp |= (((int)Z.imag >> k) & 0x01) << 4;
            tmp |= (((int)Z.real >> k) & 0x01) << 5;
	    k++;
	    tmp |= (((int)Z.imag >> k) & 0x01);
            tmp |= (((int)Z.real >> k) & 0x01) << 2;
	    k++;
	    tmp |= (((int)Z.imag >> k) & 0x01) << 1;
            tmp |= (((int)Z.real >> k) & 0x01) << 3;
	    
	    ret |= (_msbTable[tmp] << (nb - 3));
	    
	    return ret;
	}	
	return 0;
    }   

    private void _checkParameters() throws IllegalActionException {
        if(_AS0Bytes < 0) {
	    throw new IllegalActionException(this, "AS0 Bytes < 0");	
	}
	
	if(_LS0Bytes < 0) {
	    throw new IllegalActionException(this, "LS0 Bytes < 0");
	}
	
	if(_S != 1 && _S != 2 && _S != 4 && _S != 8 && _S != 16) {
	    throw new IllegalActionException(this, "\nInvalid value(" +
	        _S + ") for parameter S");
	}	

	if(_nnFast > 255) {
	    throw new IllegalActionException(this, "Maximum Fast codeword"+
	        " size is 255");
	}
	int par = _nnFast - _kkFast;
	if((par < 0) || (par > 16) || (par % 2 != 0)) {
	    throw new IllegalActionException(this, "Invalid number(" + par +
	        ") of fast parity bytes");
	}	       

	if(_nnInter > 255) {
	    throw new IllegalActionException(this, "Maximum Interleaved codeword"+
	        " size is 255");
	}
        par = _nnInter - _kkInter;
	if((par < 0) || (par > 16) || (par % 2 != 0)) {
	    throw new IllegalActionException(this, "Invalid number(" + par +
	        ") of interleaved parity bytes");
	}
        if(par % _S != 0) {
	    throw new IllegalActionException(this, "Interleaved parity " +
	        " bytes should be an integer multiple of S!");
	}    
    }

    private void _createInputInterface() throws IllegalActionException,
            NameDuplicationException { 
        if(_inputMode == 0) {
	    subChannel = new TypedIOPort[255];
	    for(int i = 0;i < 255;i++) {
	        subChannel[i] = new TypedIOPort(this, "subChannel_" + i,
	            true, false);
	        subChannel[i].setTypeEquals(BaseType.DOUBLE);
	    }
	}
	else
	if(_inputMode == 1) {
	    subChannel = new TypedIOPort[1];
	    subChannel[0] = new TypedIOPort(this, "subChannel0", true, false);
	    subChannel[0].setTypeEquals(BaseType.OBJECT);
	}
	else {
	    throw new IllegalActionException(this, "Invalid input mode");
	}	     
    }

    ///////////////////////////////////////////////////////////////////
    ////                         private variables                 ////
    
    private int _msbTable[];

    private int _bitTable[];
    private int _subChannelTable[];
    private Complex _gainTable[];
    
    private int _AS0Bytes;
    private int _LS0Bytes;
    private boolean _AS0Map;
    private boolean _LS0Map;
    private int _S;

    private int _nnFast, _kkFast;
    private int _nnInter, _kkInter;
    
    private int _inputMode;
}
